package org.swirrel;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import java.awt.Component;
import java.awt.Container;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.KeyStroke;

import org.swirrel.annotation.ActionWrapper;
import org.swirrel.installer.ActionListenerInstaller;
import org.swirrel.installer.AdjustmentListenerInstaller;
import org.swirrel.installer.CaretListenerInstaller;
import org.swirrel.installer.ChangeListenerInstaller;
import org.swirrel.installer.ComponentListenerInstaller;
import org.swirrel.installer.ContainerListenerInstaller;
import org.swirrel.installer.DocumentListenerInstaller;
import org.swirrel.installer.FocusListenerInstaller;
import org.swirrel.installer.ItemListenerInstaller;
import org.swirrel.installer.KeyListenerInstaller;
import org.swirrel.installer.ListDataListenerInstaller;
import org.swirrel.installer.ListSelectionListenerInstaller;
import org.swirrel.installer.MouseListenerInstaller;
import org.swirrel.installer.MouseMotionListenerInstaller;
import org.swirrel.installer.MouseWheelListenerInstaller;
import org.swirrel.installer.PropertyChangeListenerInstaller;
import org.swirrel.installer.SwirrelListenerInstaller;
import org.swirrel.installer.TreeModelListenerInstaller;
import org.swirrel.installer.TreeSelectionListenerInstaller;
import org.swirrel.installer.WindowListenerInstaller;
import org.swirrel.listener.SwirrelAction;
import org.swirrel.util.SwirrelException;
import org.swirrel.util.MethodCall;
import org.swirrel.installer.TableColumnModelListenerInstaller;
import org.swirrel.installer.TextListenerInstaller;
import org.swirrel.installer.UndoableEditListenerInstaller;
import org.swirrel.util.Tuple;
import org.swirrel.installer.InputMethodListenerInstaller;

public class Swirrel {

  private static List<SwirrelListenerInstaller> installers =
      Arrays.<SwirrelListenerInstaller> asList(
          new ActionListenerInstaller(),
          new AdjustmentListenerInstaller(),
          new CaretListenerInstaller(),
          new ChangeListenerInstaller(),
          new ComponentListenerInstaller(),
          new ContainerListenerInstaller(),
          new DocumentListenerInstaller(),
          new FocusListenerInstaller(),
          new InputMethodListenerInstaller(),
          new ItemListenerInstaller(),
          new KeyListenerInstaller(),
          new ListDataListenerInstaller(),
          new ListSelectionListenerInstaller(),
          new MouseListenerInstaller(),
          new MouseMotionListenerInstaller(),
          new MouseWheelListenerInstaller(),
          new PropertyChangeListenerInstaller(),
          new TableColumnModelListenerInstaller(),
          new TextListenerInstaller(),
          new TreeModelListenerInstaller(),
          new TreeSelectionListenerInstaller(),
          new UndoableEditListenerInstaller(),
          new WindowListenerInstaller());

  private static Map<MethodCall, Action> actions =
      new WeakHashMap<MethodCall, Action> ();

  private Swirrel() {
    /* do not instantiate */
  }

  public static Action getAction(boolean threaded, Component comp, Method method) {
    return actions.get(new MethodCall(threaded, method, comp));
  }

  public static void process(final Container comp) {
    try {
      for (Class clazz = comp.getClass(); !clazz.equals(Component.class);
           clazz = clazz.getSuperclass()) {
        for (Field field : clazz.getDeclaredFields()) {
          checkListeners(comp, field);
        }
      }
      for (Component subComp : comp.getComponents()) {
        if (subComp instanceof Container) {
          process( (Container) subComp);
        }
      }
    }
    catch (RuntimeException ex) {
      throw ex;
    }
    catch (Exception ex) {
      throw new SwirrelException(ex);
    }
  }

  private static void checkListeners(final Component comp, Field field) throws
      SecurityException, NoSuchMethodException, SwirrelException,
      IllegalAccessException, IllegalArgumentException {
    for (SwirrelListenerInstaller installer : installers) {
      if (installer.hasMatchingAnnotation(field)) {
        installer.validateType(field.getType());
        if (!field.isAccessible()) {
          field.setAccessible(true);
        }
        if (installer.isListenerAlreadySet(field.get(comp))) {
          continue;
        }
        List<Tuple<String, Boolean>> methodNames = installer.getMethodNames(field);
        List<MethodCall> methodCalls = new ArrayList<MethodCall> ();
        for (Tuple<String,Boolean> methodName : methodNames) {
          if (methodName.first() == null) {
            methodCalls.add(null);
          }
          else {
            methodCalls.add(findMethod(methodName.second(), comp, methodName.first()));
          }
        }
        installer.registerListener(field, comp, methodCalls);
      }
    }
  }

  private static MethodCall findMethod(boolean threaded, Component comp,
      String methodName) throws
      SecurityException, NoSuchMethodException {
    if (comp == null) {
      throw new SwirrelException("Method " + methodName + " not found");
    }
    for (Class clazz = comp.getClass(); !clazz.equals(Component.class);
         clazz = clazz.getSuperclass()) {
      for (Method method : clazz.getDeclaredMethods()) {
        if (method.getName().equals(methodName)) {
          method.setAccessible(true);
          checkForAction(method, comp);
          return new MethodCall(threaded, method, comp);
        }
      }
    }
    return findMethod(threaded, comp.getParent(), methodName);
  }

  private static void checkForAction(Method method, Component comp) {
    ActionWrapper actionWrapper = method.getAnnotation(ActionWrapper.class);
    if (actionWrapper != null) {
      MethodCall methodCall =  new MethodCall(actionWrapper.threaded(), method, comp);
      Action action = new SwirrelAction(methodCall);
      if (!"".equals(actionWrapper.acceleratorKey())) {
        action.putValue(Action.ACCELERATOR_KEY,
                        KeyStroke.getKeyStroke(actionWrapper.acceleratorKey()));
      }
      if (!"".equals(actionWrapper.actionCommandKey())) {
        action.putValue(Action.ACTION_COMMAND_KEY,
                        actionWrapper.actionCommandKey());
      }
      if (!"".equals(actionWrapper.largeIcon())) {
        action.putValue("SwingLargeIconKey", //Java 1.5 fix, Action.LARGE_ICON_KEY is Java 1.6
                        getIcon(comp.getClass(), actionWrapper.largeIcon()));
      }
      if (!"".equals(actionWrapper.longDescription())) {
        action.putValue(Action.LONG_DESCRIPTION, actionWrapper.longDescription());
      }
      if (java.awt.event.KeyEvent.CHAR_UNDEFINED != actionWrapper.mnemonicKey()) {
        action.putValue(Action.MNEMONIC_KEY,actionWrapper.mnemonicKey());
      }
      if (!"".equals(actionWrapper.name())) {
        action.putValue(Action.NAME, actionWrapper.name());
      }
      if (!"".equals(actionWrapper.shortDescription())) {
        action.putValue(Action.SHORT_DESCRIPTION,
                        actionWrapper.shortDescription());
      }
      if (!"".equals(actionWrapper.smallIcon())) {
        action.putValue(Action.SMALL_ICON,
                        getIcon(comp.getClass(), actionWrapper.smallIcon()));
      }
      actions.put(methodCall, action);
    }
  }

  private static ImageIcon getIcon(Class clazz, String iconPath) {
    return new ImageIcon(clazz.getResource(iconPath));
  }
}
